
// 若使用XILINX的IOBUF需要使能该选项
// `define USE_XILINX_IOBUF
// 若需要使用Xilinx的ILA进行调试需要使能该选项
// `define USE_ILA_DEBUGGER

module MDIO_INTERFACE
#(
    // 总线时钟
    parameter CLK_FREQ = 'd50_000_000,
    // MDIO时钟
    parameter MDC_FREQ = 'd5_000_000 
)
(
    input           CLK,
    input           RSTN,

    input   [4:0]   PHY_ADDR,
    input   [4:0]   REG_ADDR,
    input           REN,
    output  [15:0]  RDATA,
    input           WEN,
    input   [15:0]  WDATA,
    output          BUSY,
    output          DONE,
    // MDIO输出
    output          MDC,
    inout           MDIO
    
);


reg         mdc;
wire        mdio_i;
reg         mdio_o;
reg         mdio_oe;
reg [9:0]   phyreg_addr;
reg [15:0]  wdata;
reg [15:0]  rdata;
reg         w_flag;
reg         r_flag;
wire        busy_flag;
assign busy_flag = w_flag | r_flag;

// ILA 用于调试
`ifdef USE_ILA_DEBUGGER
ila_0 ila0_inst
(
    .clk        (CLK),
    .probe0     (mdio_i),
    .probe1     (mdio_o),
    .probe2     (mdio_oe),
    .probe3     (mdc),
    .probe4     (phyreg_addr),
    .probe5     (rdata),
    .probe6     (wdata),
    .probe7     (mdc_cnt)
);
`endif

// 捕获REN与WEN上升沿
reg ren,wen;
wire r_start,w_start;
always @(posedge CLK) begin
    ren <= REN;
    wen <= WEN;
end
assign r_start = ({REN,ren} == 2'b10);
assign w_start = ({WEN,wen} == 2'b10);

// MDIO三态门
`ifdef USE_XILINX_IOBUF
IOBUF #(
    .DRIVE(12),             // Specify the output drive strength
    .IBUF_LOW_PWR("TRUE"),  // Low Power - "TRUE", High Performance = "FALSE" 
    .IOSTANDARD("LVCMOS33"),// Specify the I/O standard
    .SLEW("SLOW")           // Specify the output slew rate
) IOBUF_inst (  
    .O(mdio_i),     // Buffer output
    .IO(MDIO),      // Buffer inout port (connect directly to top-level port)
    .I(mdio_o),     // Buffer input
    .T(~mdio_oe)    // 3-state enable input, high=input, low=output
);
`else
assign mdio_i = mdio_oe?1'b0:MDIO;
assign MDIO = mdio_oe?mdio_o:1'bz;
`endif

// 生成MDIO时钟并捕获上升沿与下降沿
localparam CLK_DIV_FACTOR = CLK_FREQ / MDC_FREQ / 2 - 1;
reg [11:0]mdc_div_cnt;
wire mdc_pedge = (mdc_div_cnt == 'd0) && mdc == 1'b0;
wire mdc_nedge = (mdc_div_cnt == 'd0) && mdc == 1'b1;
always @(posedge CLK) begin
    if(~RSTN) begin
        mdc_div_cnt <= CLK_DIV_FACTOR;
        mdc <= 0;
    end
    else begin
        if(mdc_div_cnt == 12'd0) begin
            mdc_div_cnt <= CLK_DIV_FACTOR;
            mdc <= ~mdc;
        end
        else begin
            mdc_div_cnt <= mdc_div_cnt - 'd1;
        end
    end
end

// 对MDC下降沿进行计数
localparam C22_MDC_CNT = 'd64;
reg [11:0] mdc_cnt;
reg mdc_cnt_en;
wire mdc_cnt_end;
assign mdc_cnt_end = (mdc_cnt == C22_MDC_CNT) && mdc_nedge;
always @(posedge CLK) begin
    if(~RSTN) begin
        mdc_cnt <= 12'd0;
    end
    else begin
        if(mdc_cnt_en && mdc_nedge) begin
            if(mdc_cnt == C22_MDC_CNT) begin
                mdc_cnt <= 'd0;
            end
            else begin
                mdc_cnt <= mdc_cnt + 'd1;
            end
        end
    end
end

always @(posedge CLK) begin
    if(~RSTN) begin
        mdc_cnt_en <= 0;
    end
    else begin
        if(r_start || w_start) begin
            mdc_cnt_en <= 1;
        end
        else if(mdc_cnt_end) begin
            mdc_cnt_en <= 0;
        end
    end
end

// 锁存数据避免在中途改变
always @(posedge CLK) begin
    if(~RSTN) begin
        phyreg_addr <= 10'b0;
        wdata <= 10'b0;
    end
    else begin
        if(~busy_flag && (r_start || w_start)) begin
           phyreg_addr <= {PHY_ADDR,REG_ADDR};
           wdata <= WDATA; 
        end
    end
end

// 读优先，控制读写标志位
always @(posedge CLK) begin
    if(~RSTN) begin
        r_flag <= 0; w_flag <= 0;
    end
    else if(mdc_cnt_end) begin
        r_flag <= 0; w_flag <= 0;
    end
    else if(r_start && ~busy_flag) begin
        r_flag = 1; w_flag <= 0;
    end
    else if(w_start && ~busy_flag) begin
        r_flag = 0; w_flag <= 1;
    end
end

// 线性序列机控制输出
always @(posedge CLK) begin
    if(~RSTN) begin
        mdio_o <= 1;
        mdio_oe <= 1;
    end
    else if(mdc_nedge) begin
        case(mdc_cnt)
            0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31:
            begin
                mdio_oe <= 1;
                mdio_o <= 1;
            end
            32: begin mdio_o <= 0; end
            33: begin mdio_o <= 1; end
            34: begin mdio_o <= r_flag; end
            35: begin mdio_o <= w_flag; end
            // PHY地址与寄存器地址段
            36: begin mdio_o <= phyreg_addr[9]; end
            37: begin mdio_o <= phyreg_addr[8]; end
            38: begin mdio_o <= phyreg_addr[7]; end
            39: begin mdio_o <= phyreg_addr[6]; end
            40: begin mdio_o <= phyreg_addr[5]; end
            41: begin mdio_o <= phyreg_addr[4]; end
            42: begin mdio_o <= phyreg_addr[3]; end
            43: begin mdio_o <= phyreg_addr[2]; end
            44: begin mdio_o <= phyreg_addr[1]; end
            45: begin mdio_o <= phyreg_addr[0]; end
            // TA段
            46: begin mdio_oe <= w_flag; mdio_o <= 1; end
            47: begin mdio_o <= 0; end
            48: begin mdio_o <= wdata[15]; end
            49: begin mdio_o <= wdata[14]; end
            50: begin mdio_o <= wdata[13]; end
            51: begin mdio_o <= wdata[12]; end
            52: begin mdio_o <= wdata[11]; end
            53: begin mdio_o <= wdata[10]; end
            54: begin mdio_o <= wdata[9]; end
            55: begin mdio_o <= wdata[8]; end
            56: begin mdio_o <= wdata[7]; end
            57: begin mdio_o <= wdata[6]; end
            58: begin mdio_o <= wdata[5]; end
            59: begin mdio_o <= wdata[4]; end
            60: begin mdio_o <= wdata[3]; end
            61: begin mdio_o <= wdata[2]; end
            62: begin mdio_o <= wdata[1]; end
            63: begin mdio_o <= wdata[0]; end
        endcase
    end
end

reg rdata_fetch_flag;
always @(posedge CLK) begin
    if(~RSTN) rdata_fetch_flag <= 0;
    else begin
        if(mdc_pedge && mdc_cnt == 'd48) rdata_fetch_flag <= 1;
        else if(mdc_nedge && mdc_cnt == C22_MDC_CNT) rdata_fetch_flag <= 0;
    end
end

always @(posedge CLK) begin
    if (~RSTN) rdata <= 16'h0;
    else if(rdata_fetch_flag && mdc_pedge) begin
        rdata <= {rdata[14:0],mdio_i};
    end
end

assign RDATA = rdata;
assign MDC = mdc;
assign BUSY = busy_flag;
assign DONE = mdc_cnt_end;

endmodule